
--[[
Features: 
- Populate a DB of min/max values for autohits received from mobs.
- Seperate entries for seperate AP values due to buffs/debuffs.

/dump OneHitWonder.db.global.mobdb
--]]

local ADDON_NAME = "OneHitWonder"
OneHitWonder = LibStub("AceAddon-3.0"):NewAddon(ADDON_NAME, "AceConsole-3.0", "AceEvent-3.0")
local guidreg = LibStub("LibGUIDRegistry-0.1")
function NameFromGUID(guid)
	if not guid then return 'nil' end
	return guidreg:GetName(guid) or guid
end

-- TODO
o = OneHitWonder
local L = {}
local db
local CURRENT_DB_VERSION = 5
local mobdb

local ohw = OneHitWonder
do
	-- ugly but works -.-
	local defaultprint = OneHitWonder.Print
	function OneHitWonder:Print(...)
		-- Try to find the Debugframe.
		for i=1,NUM_CHAT_WINDOWS do
			local title = getglobal("ChatFrame" .. i .. "TabText"):GetText()
			if title == 'DebugFrame' then
				-- found it, rehook Print
				local frame = getglobal("ChatFrame" .. i)
				function OneHitWonder:Print(...)
					defaultprint(o, frame, ...)
				end
				self:Print('hooked Print.')
				self:Print(...)
				return
			end
		end
		-- not found, use the default frame
		--defaultprint(self,...)
	end
end



local defaultSettings = {
    profile = {
    },
	 global = {
		 --version = CURRENT_DB_VERSION, -- If we make that a default, it wouldn't be saved.
		 mobdb = {
			 --[[
			 [mobType] = {
				 name = <name>,
				 abilities = {
					 [spellID] = true or 'ignore',
				 }, 
				 level = { -- level specific data
					 [level] = {
						 melee = {
							 [apmod] = {
								 min = min hit,
								 max = max hit,
								 num = number of hits,
								 total = sum of avg damage,
								 totalSqr = sum of avg damage squared,
								 school = ,
								 cancrush = true or nil, 
							 },
						 }
					 },
				 },
			 }
			 --]]
		 },
	 },
}

local options = { 
	name = ADDON_NAME,
	handler = OneHitWonder,
	type = 'group',
	args = {
		mobs={
			name="Mobs",
			desc="Browse the mob data.",
			type="execute",
			func = 'ShowDBBrowser',
		},
		scan={
			name="Scan",
			desc="Scan raid member's talents.",
			type="execute",
			func = 'UpdateUnitsToScan',
		},
	},
}

function OneHitWonder:OnInitialize()
	-- Called when the addon is loaded
	self.db = LibStub("AceDB-3.0"):New("OneHitWonderDB", defaultSettings, "Default")
	db = self.db
	mobdb = db.global.mobdb

	LibStub("AceConfig-3.0"):RegisterOptionsTable(ADDON_NAME, options, {'ohw'})

	if db.global.version ~= CURRENT_DB_VERSION then
		self:RegisterEvent("VARIABLES_LOADED", "ConvertDB")
	end

	self:InitializeGUI()
end

local playerGUID
local OnDragStart, OnDragStop

function OneHitWonder:OnEnable()
	playerGUID = UnitGUID('player')
	self:Printf('playerGUID %s',playerGUID)
   -- Called when the addon is enabled
   self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
   self:RegisterEvent("PLAYER_TARGET_CHANGED")
   self:RegisterEvent("UNIT_HEALTH")
	self:RegisterEvent("PLAYER_LOGIN")
	self:RegisterEvent("PLAYER_ALIVE")

   --self:RegisterEvent("UNIT_STATS", "OnTestEvent")
   --self:RegisterEvent("UNIT_DAMAGE", "OnTestEvent")
   --self:RegisterEvent("UNIT_ATTACK_POWER", "OnTestEvent")
   --self:RegisterEvent("PLAYER_DAMAGE_DONE_MODS", "OnTestEvent")
   self:RegisterEvent("UNIT_RESISTANCES")

	OneHitWonderStatsFrame:RegisterForDrag("LeftButton")
	OneHitWonderStatsFrame:SetScript('OnDragStart', OnDragStart)
	OneHitWonderStatsFrame:SetScript('OnDragStop', OnDragStop)
end

function OneHitWonder:moblist()
	for mobType, data in pairs(mobdb) do
		self:Printf('%s %i', data.name, mobType)
	end
end

function OnDragStart(frame)
	frame:StartMoving()
end

function OnDragStop(frame)
	frame:StopMovingOrSizing()
end

-- When we just logged in, PLAYER_LOGIN is fired, then PLAYER_ALIVE. Talent information, however, 
-- is only available on PLAYER_ALIVE. When the UI is reloaded, PLAYER_ALIVE is not fired, and we 
-- have to use PLAYER_LOGIN. It's pretty stupid.
function OneHitWonder:PLAYER_LOGIN()
	self:CheckPlayerTalents()
end
function OneHitWonder:PLAYER_ALIVE()
	self:CheckPlayerTalents()
end

function OneHitWonder:UNIT_RESISTANCES(event, unit)
	if unit=='player' then
		-- Fired when the player's armor has changed.
		self:UpdateTargetStats()
	end
end

function OneHitWonder:ConvertDB()
	if not db.global.version or db.global.version < 4 then
		self:Print('upgrading db to version 4')
		self:ClearMobDB()
		db.global.version = 4
	end
	if db.global.version == 4 then
		local count=0
		for mobType, mobdata in pairs(db.global.mobdb) do
			mobdata.abilities = {}
			count = count+1
		end
		self:Printf('upgraded db to version 5, %i db entries.', count)

		db.global.version = 5
	end
end

function OneHitWonder:OnTestEvent(...)
	self:Print(...)
end

function OneHitWonder:OnDisable()
    self:UnregisterAllEvents()
end

local function GetMobType(guid)
	if not guid then return end
	local srcType = strsub(guid, 1+2+2, 1+2+2)
	if srcType~='3' then
		return
	end
	local typeString = strsub(guid, 1+2+3+3, 1+2+3+3+4-1)
	return tonumber(typeString, 16)
end
OneHitWonder.GetMobType = GetMobType

function OneHitWonder:Tracef(...)
	self:Print(format(...))
end

function OneHitWonder:Printf(...)
	self:Print(format(...))
end

function OneHitWonder:ClearMobDB()
	mobdb = {}
	db.global.mobdb = mobdb
end

function OneHitWonder:AddMobType(mobType, mobName, mobLevel)
	local typeDB = mobdb[mobType]
	local newtype
	if not typeDB then
		typeDB = { name = mobName, level = {}, abilities = {}}
		mobdb[mobType] = typeDB
		self:Printf('new mob type %i, name %s', mobType, mobName)
		newtype = true
	end
	if not typeDB.level[mobLevel] then
		self:Printf('new mob level %i for %s', mobLevel, mobName)
		typeDB.level[mobLevel] = { melee = {}, }
	end
	if newtype then
		self:UpdateMobList()
	end
end

function OneHitWonder:UNIT_HEALTH(event, unitid)
	if unitid == 'player' then
		self:UpdatePlayerHealth()
	end
end
function OneHitWonder:PLAYER_TARGET_CHANGED()
	local guid = UnitGUID('target')
	if not guid then 
		-- no target
		self:HideTargetStats()
		return 
	end
	if not UnitCanAttack('target', 'player') then
		-- TODO: What about Kael'thas?
		self:HideTargetStats()
		return
	end
	local mobType = GetMobType(guid)
	if not mobType then 
		-- not an NPC
		self:HideTargetStats()
		return 
	end
	local mobName = UnitName('target')
	local mobLevel = UnitLevel('target')
	if mobLevel==-1 then
		if UnitLevel('player')==70 then
			mobLevel = 73
		else
			return
		end
	end

	self:AddMobType(mobType, mobName, mobLevel)

	if self.gui and self.gui:IsShown() then
		self:ShowStats(mobType)
	end
	self:ShowTargetStats()
end


--[[
A list of all the player talents that are interesting.
--]]
local talentmodifiers = {
	demoshout = {
		-- Improved Demoralizing Shout
		class = 'WARRIOR',
		tab = 2,
		talent = 3,
		multipliers = {
			[0] = 1,
			[1] = 1.08,
			[2] = 1.16,
			[3] = 1.24,
			[4] = 1.32,
			[5] = 1.40,
		},
	},
	defensivestance = {
		-- Improved Defensive Stance
		class = 'WARRIOR',
		tab = 3,
		talent = 18,
		multipliers = {
			[0] = 1,
			[1] = 0.98,
			[2] = 0.96,
			[3] = 0.94,
		},
	},
	demoroar = {
		class = 'DRUID',
		-- Ferocity
		tab = 2,
		talent = 2,
		multipliers = {
			[0] = 1,
			[1] = 1.08,
			[2] = 1.16,
			[3] = 1.24,
			[4] = 1.32,
			[5] = 1.40,
		},
	},
	weakness = {
		class = 'WARLOCK',
		-- Improved Curse of Weakness
		tab = 1,
		talent = 3,
		multipliers = {
			[0] = 1,
			[1] = 1.10,
			[2] = 1.20,
		},
	},
}	


local talentcache = { --[[
	[guid] = {
		[spellname] = multiplier -- Note: This is the internal name, as in the talentmodifiers table.
	}--]]
}
-- Fill the talent cache.
do
	--  We need to maintain a list of all the people we didn't scan.
	local unitsToScan = {}
	local classesToScan = {WARRIOR=true,DRUID=true,WARLOCK=true}
	local scanner
	local ScanGroupForTalents
	function OneHitWonder:UpdateUnitsToScan()
		local prefix
		local num
		if GetNumRaidMembers()>0 then
			prefix = 'raid'
			num = GetNumRaidMembers()
		elseif GetNumPartyMembers()>0 then
			prefix = 'party'
			num = GetNumPartyMembers()
		else
			-- nothing to do
			return
		end
		unitsToScan = {}
		local empty = true
		for i=1,num do
			local unit = prefix..i
			local class = select(2, UnitClass(unit))
			local guid = UnitGUID(unit)
			if classesToScan[class] and not talentcache[guid] then
				-- Talents unknown, add it to the list.
				tinsert(unitsToScan, unit)
				empty = false
				self:Tracef("adding %s to unitsToScan.", UnitName(unit))
			end
		end
		if not empty then
			-- Start scanning.
			if not scanner then
				scanner = CreateFrame('Frame')
				scanner:SetScript('OnUpdate', ScanGroupForTalents)
			end
			OneHitWonder:Tracef("UpdateUnitsToScan: Starting scanner.")
			scanner:Show()
		else
			-- Stop scanning.
			if scanner then 
				OneHitWonder:Tracef("UpdateUnitsToScan: Nothing to scan, stopping scanner.")
				scanner:Hide() 
			end
		end
	end

	local inspecting, inspectTime
	-- Scan.
	function ScanGroupForTalents()
		if inspectTime then
			if GetTime()-inspectTime<3.0 then
				-- We're currently waiting for INSPECT_TALENT_READY. Wait for 3s.
				return
			else
				OneHitWonder:Tracef("INSPECT_TALENT_READY didn't fire.")

			end
		end
		for i,unit in ipairs(unitsToScan) do
			if CheckInteractDistance(unit, 1) then
				inspectTime = GetTime()
				inspecting = i
				OneHitWonder:RegisterEvent('INSPECT_TALENT_READY')
				NotifyInspect(unit)
				return
			end
		end
		-- No one is in range. TODO throttle?
	end

	function OneHitWonder:INSPECT_TALENT_READY(...)
		self:UnregisterEvent('INSPECT_TALENT_READY')
		local unit = unitsToScan[inspecting]
		self:Tracef('inspecting %s (%s).', UnitName(unit), unit)
		self:CheckPlayerTalents(unit)
		tremove(unitsToScan, inspecting)
		if #unitsToScan == 0 then
			-- Stop scanning.
			if scanner then 
				OneHitWonder:Tracef("INSPECT_TALENT_READY: All scanned, stopping scanner.")
				scanner:Hide() 
			end
		end
		inspecting = nil
		inspectTime = nil
	end

	function OneHitWonder:CheckPlayerTalents(unit)
		local inspect
		if unit then
			inspect = true
		else
			unit = 'player'
		end
		local guid = UnitGUID(unit)
		local class = select(2,UnitClass(unit))
		if not talentcache[guid] then
			for spellName, talentmod in pairs(talentmodifiers) do
				if talentmod.class == class then
					local multiplier = 1
					local nameTalent, _, tier, column, currentRank, _, _, _ = GetTalentInfo(talentmod.tab, talentmod.talent, inspect)
					if not nameTalent then
						return 
					end
					if currentRank then
						multiplier = talentmod.multipliers[currentRank]
						self:Tracef('%i points in talent %s, modifies %s by %+i%%.', currentRank, nameTalent, spellName, 100*(multiplier-1))
					end
					if not talentcache[guid] then
						talentcache[guid] = {}
					end
					talentcache[guid][spellName] = multiplier
				end
			end
		end
		return 1
	end
end



--[[
A list of all the player debuffs that modify a mob's melee damage.
--]]
local apauras = {
	[676] = {
		name = 'disarm', 
		rank = 1,
		duration = 10,
	},
	[1160] = {
		name = 'demoshout',
		rank = 1,
		apmod = -35,
		duration = 30,
		talented = true,
	},
	[25203] = {
		name = 'demoshout',
		rank = 7,
		apmod = -300,
		duration = 30,
		talented = true,
	},
	[27051] = {
		name = 'demoroar',
		rank = 6,
		apmod = -240,
		duration = 30,
		talented = true,
	},
	[27051] = {
		name = 'screech',
		rank = 5,
		apmod = -210,
		duration = 4,
	},
	[702] = {
		name = 'weakness',
		rank = 1,
		apmod = -21,
		duration = 120,
		talented = true,
	},
	[1108] = {
		name = 'weakness',
		rank = 2,
		apmod = -41,
		duration = 120,
		talented = true,
	},
	[6205] = {
		name = 'weakness',
		rank = 3,
		apmod = -64,
		duration = 120,
		talented = true,
	},
	[7646] = {
		name = 'weakness',
		rank = 4,
		apmod = -82,
		duration = 120,
		talented = true,
	},
	[11707] = {
		name = 'weakness',
		rank = 5,
		apmod = -123,
		duration = 120,
		talented = true,
	},
	[11708] = {
		name = 'weakness',
		rank = 6,
		apmod = -163,
		duration = 120,
		talented = true,
	},
	[27224] = {
		name = 'weakness',
		rank = 7,
		apmod = -257,
		duration = 120,
		talented = true,
	},
	[30909] = {
		name = 'weakness',
		rank = 8,
		apmod = -350,
		duration = 120,
		talented = true,
	},
	[27226] = {
		name = 'recklessness',
		rank = 5,
		apmod = 135,
		duration = 120,
	}
}
do
	local mobcache = { --[[
	[guid] = {
		apmod = ...,
		auras = {
			[spellID] = {
				apmod = number or nil
				caster = GUID or 'unknown' if the caster matters, 'someone' if it doesn't.
			}
		}
	}
	--]]
	}
	local castcache = { --[[
		[spellID] = {
			[1..3] = { -- sorted by timestamp
				source = GUID or nil, 
				target = GUID or nil, 
				timestamp = timestamp 
			}
		}--]]
	}

	-- Returns the current AP modifier for the mob.
	-- This value is used as an index. Complications arise when a debuff is modified by talents.
	-- If the source of such a modifier is unknown, there's nothing we can do, just discard it.
	-- If the source is known, but the talent information is not, we can determine and use this 
	-- information later. But we still need to store the hit data somewhere.
	function OneHitWonder:MobAPModifier(guid)
		local mobdata = mobcache[guid]
		if not mobdata then
			return 0
		end
		return mobdata.apmod
	end

	-- Updates mobcache[guid].apmod. Called whenever an aura is added or removed.
	function OneHitWonder:UpdateAP(guid)
		if not guid or not mobcache[guid] then 
			return 
		end
		local mobdata = mobcache[guid]
		local apmod = 0
		local buffs
		for spellID, spellData in pairs(mobdata.auras) do
			local caster = spellData.caster
			if caster == 'unknown' then
				-- Modified by talents, caster unknown.
				mobdata.apmod = nil
				return
			elseif apauras[spellID] and spellData.apmod then
				apmod = apmod + spellData.apmod
			else
				-- AP modifier unknown
				buffs = (buffs or '')..'+'..tostring(spellID)
			end
		end
		if buffs then
			mobdata.apmod = tostring(apmod)..buffs
		else
			mobdata.apmod = apmod
		end
		self:UpdateTargetStats()
	end

	-- Remembers srcGUID as the caster of spellID.
	-- For each spell, the last NUM_CASTS casts are remembered.
	local NUM_CASTS = 3
	function OneHitWonder:MobAuraCast(timestamp, destGUID, spellID, srcGUID)
		-- Inserts a table at the end of the castcache, which is a queue (FIFO). So the last entry is the most recent.
		if not srcGUID then 
			self:Printf('MobAuraCast: srcGUID is nil.')
			return
		end
		if not spellID then 
			self:Printf('MobAuraCast: spellID is nil.')
			return
		end
		local spellName
		if apauras[spellID] then
			spellName =apauras[spellID].name 
			if not apauras[spellID].talented then
				-- the caster doesn't matter
				return
			end
		else
			spellName = GetSpellInfo(spellID)
		end

		if not castcache[spellID] then
			-- Initialize.
			castcache[spellID] = {}
		elseif #castcache[spellID]==numcasts then
			-- Remove the first entry.
			tremove(castcache[spellID], 1)
		end
		-- Insert at the end.
		self:Tracef('MobAuraCast: Added caster %s of %s, target = %s.', NameFromGUID(srcGUID), spellName, NameFromGUID(destGUID))
		tinsert(castcache[spellID], { source = srcGUID, target = destGUID, timestamp = timestamp })
	end

	-- Adds the AP modifying aura to the mob.
	function OneHitWonder:MobAddAura(timestamp, guid, spellID)
		local mobdata = mobcache[guid]
		if not mobdata then
			mobdata = { auras = {}, apmod = 0, }
			mobcache[guid] = mobdata
		end
		local apaura = apauras[spellID]
		if apaura then 
			if mobdata.auras[spellID] then
				self:Printf('MobAddAura: Aura %i %s(rank %i) was already present on %s.', spellID, apaura.name, apaura.rank, NameFromGUID(guid))
				self:MobRemoveAura(timestamp, guid, spellID)
			end

			local apmod = apaura.apmod
			local caster
			if apaura.talented then
				-- The spell is affected by talents. 
				caster = 'unknown'
				-- Try to identify the source of this debuff.
				local bestfit
				if castcache[spellID] then
					-- Iterate through all the casts in order. The most recent cast comes last.
					for _,c in ipairs(castcache[spellID]) do
						if not c.target or c.target==guid then
							-- Either it was an AoE spell, or the target was our mob.
							-- This accounts for two warlocks casting the same curse on two different mobs at the same time.
							if timestamp-c.timestamp < 1.5 then
								-- It happened less than 1.5s ago.
								bestfit = c
							end
						end
					end
				end
				-- TODO refactor
				if bestfit and talentcache[bestfit.source] then
					caster = bestfit.source
					local talentmod = talentcache[caster]
					-- TODO rounding
					apmod = apmod * talentmod[apaura.name]
				end
			else
				-- The spell is not affected by talents, so the caster doesn't matter.
				caster = 'someone'
			end
			mobdata.auras[spellID] = {
				apmod = apmod,
				caster = caster,
			}
			self:UpdateAP(guid)
			local apstring = tostring(mobdata.apmod or 'unknown')
			self:Tracef('MobAddAura: %s from %s added to %s, new ap mod is %s.', apaura.name, NameFromGUID(caster), NameFromGUID(guid), apstring)
		else
			-- check for mob self buffs
			local mobType = GetMobType(guid)
			if mobdb[mobType] and mobdb[mobType].abilities[spellID] == 'ignore' then
				self:Tracef('MobAddAura: %s ignored on %s.', select(1,GetSpellInfo(spellID)), NameFromGUID(guid))
				return
			end
			local spellcache = castcache[spellID]
			local last
			if spellcache then
				last = spellcache[#spellcache]
			end
			if last and last.source == guid then
				self:Tracef('MobAddAura: %s added to %s.', select(1,GetSpellInfo(spellID)), NameFromGUID(guid))
				mobdata.auras[spellID] = {
					caster = caster,
				}
				self:UpdateAP(guid)
			end
		end
	end

	-- Removes the AP modifying aura from the mob.
	function OneHitWonder:MobRemoveAura(timestamp, guid, spellID)
		local mobdata = mobcache[guid]
		if not mobdata then
			self:Printf('MobRemoveAura: No data for mob %s.', guid)
			return
		end
		local apaura = apauras[spellID]
		if apaura then 
			if mobdata.auras[spellID] then
				local apmod = mobdata.auras[spellID].apmod
				mobdata.auras[spellID] = nil
				self:Tracef('MobRemoveAura: %s removed from %s, modified by %s.', apaura.name, NameFromGUID(guid), tostring(apmod or 'unknown'))
				self:UpdateAP(guid)
			else
				self:Printf('MobRemoveAura: Aura %i %s(rank %i) is not present on %s.', spellID, apaura.name, apaura.rank, NameFromGUID(guid))
			end
		else
			if mobdata.auras[spellID] then
				self:Tracef('MobRemoveAura: %s removed from %s.', select(1,GetSpellInfo(spellID)), NameFromGUID(guid))
				mobdata.auras[spellID] = nil
				self:UpdateAP(guid)
			end
		end
	end

	-- TODO on leave combat
	function OneHitWonder:MobClearAll()
		self:Tracef('MobClearAll: removing all mob data.')
		mobcache = {}
	end

	-- 
	function OneHitWonder:MobClear(guid)
		if mobcache[guid] then
			self:Tracef('MobClear: removing data for %s',NameFromGUID(guid))
		end
		mobcache[guid] = nil
	end

	-- Adds the spell to the mob's type.
	function OneHitWonder:StoreMobCast(srcGUID, spellID)
		local mobType = GetMobType(srcGUID)
		local typeDB = mobdb[mobType]
		if not typeDB then return end
		if not typeDB.abilities[spellID] then
			self:Tracef('StoreMobCast: Adding ability %s to mob type %s.', GetSpellLink(spellID), NameFromGUID(srcGUID))
			typeDB.abilities[spellID] = true
		end
	end

	-- Stored in mobdb[GetMobType(srcGUID)] [srcLevel] .melee [apmod]
	function OneHitWonder:StoreMeleeHit(timestamp, srcGUID, srcLevel, rawDamageMin, rawDamageMax, school, crushing)
		local apmod = self:MobAPModifier(srcGUID)
		if not apmod then
			-- discard
			return
		end
		local mobType = GetMobType(srcGUID)
		local typeDB = mobdb[mobType]
		if not typeDB or not typeDB.level[srcLevel] then
			return
		end
		local meleeDB = typeDB.level[srcLevel].melee
		local avg = 0.5*(rawDamageMin + rawDamageMax)
		if not meleeDB[apmod] then
			meleeDB[apmod] = { min = rawDamageMax, max = rawDamageMin, num = 1, total = avg, totalSqr = avg*avg, school = school }
		else
			local apdb = meleeDB[apmod]
			if not apdb.school then
				apdb.school = school
			end
			if school == apdb.school then
				apdb.num = apdb.num+1
				apdb.total = apdb.total+avg
				local sqr = avg*avg
				apdb.totalSqr = apdb.totalSqr+sqr
				apdb.min = math.min(apdb.min, rawDamageMax)
				apdb.max = math.max(apdb.max, rawDamageMin)
			end
		end
		if crushing then
			meleeDB[apmod].cancrush = true
		end
		self:UpdateTargetStats()
	end
end

do
	--
	local cleaves = {
		[29832] = true, -- Attumen the Huntsman
		[31043] = true, -- Tinhead
		[30131] = true, -- Nightbane
		[30619] = true, -- Magtheridon
		-- [31436] = true, -- Kaz'rogal instant!
		[31345] = true, -- Azgalor

		[15284] = true, -- multiple
		[15496] = true, -- multiple
		[19983] = true, -- dragons
		[43273] = true, -- Amani'shi Warbringer
		[46468] = true, -- Shadowsword Vanquisher
		[38572] = true, -- Vashj'ir Honor Guard
	}
	function OneHitWonder:COMBAT_LOG_EVENT_UNFILTERED(_,timestamp, event, srcGUID,srcName,srcFlags, destGUID,destName,destFlags, ...)
		--self:Print(_,timestamp, event, srcGUID,srcName,srcFlags, destGUID,destName,destFlags, ...)

		local targetGUID = UnitGUID('target')
		if srcGUID==targetGUID then
			-- Target melee swings
			if event == 'SWING_DAMAGE' or event == 'SWING_MISSED'
				or (
					(event == 'SPELL_MISSED' or event == 'SPELL_DAMAGE')
					and cleaves[select(1,...)]
				) then
				self:OnTargetSwing(timestamp)
			end
		elseif destGUID==targetGUID then
			-- Target parries.
			local missType
			if event == 'SWING_MISSED' then 
				missType = select(1, ...)
			elseif event == 'SPELL_MISSED' then
				missType = select(4, ...)
			end
			if missType then 
				self:OnTargetParry(timestamp)
			end
		end
		if self[event] then
			--self:Tracef('%s.%i %s', date('%H:%M:%S',timestamp), (timestamp-floor(timestamp))*1000, event)
			self[event](self,timestamp, srcGUID,srcName,srcFlags, destGUID,destName,destFlags, ...)
		end
	end
end

function OneHitWonder:SPELL_AURA_APPLIED(timestamp, srcGUID,srcName,srcFlags, destGUID,destName,destFlags, 
	spellID, spellName, spellSchool, auraType)
	local mobType = GetMobType(destGUID)
	if mobType then
		-- NPCs
		local apaura = apauras[spellID]
		if apaura or auraType=='BUFF' then
			self:MobAddAura(timestamp, destGUID, spellID)
		end
	end
end

function OneHitWonder:SPELL_CAST_SUCCESS(timestamp, srcGUID,srcName,srcFlags, destGUID,destName,destFlags, 
	spellID, spellName, spellSchool)
	if apauras[spellID] then
		self:Print("SPELL_CAST_SUCCESS",timestamp, srcGUID,srcName,srcFlags, destGUID,destName,destFlags, 
		spellID, spellName, spellSchool)
		-- if the source is a player, and the target is a mob
		if destName and not GetMobType(destGUID) then
			return
		end
		self:MobAuraCast(timestamp, destName and destGUID, spellID, srcGUID)
	elseif GetMobType(srcGUID) then
		self:Print("SPELL_CAST_SUCCESS",timestamp, srcGUID,srcName,srcFlags, destGUID,destName,destFlags, 
		spellID, spellName, spellSchool)
		self:MobAuraCast(timestamp, destName and destGUID, spellID, srcGUID)
		self:StoreMobCast(srcGUID, spellID)
	end
end
function OneHitWonder:SPELL_CAST_START(timestamp, srcGUID,srcName,srcFlags, destGUID,destName,destFlags, 
	spellID, spellName, spellSchool)
	if srcName and GetMobType(srcGUID) then
		self:StoreMobCast(srcGUID, spellID, true)
	end
end
function OneHitWonder:SPELL_SUMMON(timestamp, srcGUID,srcName,srcFlags, destGUID,destName,destFlags, 
	spellID, spellName, spellSchool)
	if srcName and GetMobType(srcGUID) then
		self:StoreMobCast(srcGUID, spellID, true)
	end
end

function OneHitWonder:SPELL_AURA_REMOVED(timestamp, srcGUID,srcName,srcFlags, destGUID,destName,destFlags, 
	spellID, spellName, spellSchool, auraType)
	local mobType = GetMobType(destGUID)
	if mobType then
		-- NPCs
		--local apaura = apauras[spellID]
		--if apaura then
			self:MobRemoveAura(timestamp, destGUID, spellID)
		--end
	end
end

--[[
7/20 22:33:11.187  UNIT_DIED,0x0000000000000000,nil,0x80000000,0xF130005AC3001E80,"Klingenwüter der Knochenmalmer",0x8010a48
--]]
function OneHitWonder:UNIT_DIED(timestamp, srcGUID,srcName,srcFlags, destGUID,destName,destFlags)
	OneHitWonder:MobClear(destGUID)
end

function OneHitWonder:GetDamageMultipliers(srcLevel, school)
	local rawMultiplier = 1
	local armorMultiplier = 1
	if school == 1 then
		-- Apply damage reduction by armor.
		local armor = UnitArmor('player')
		local base 
		-- reduction = armor/(armor+base)
		if srcLevel<60 then
			base = 85*srcLevel+400
		else
			-- 62 -> 6817.5
			base = 467.5*srcLevel-22167.5
		end
		-- The raw damage is multiplied by 1-reduction = (armor+base - armor)/(armor+base) = base/(armor+base),
		-- damage taken = raw damage * armorMultiplier
		armorMultiplier = base/(base+armor)
		rawMultiplier = armorMultiplier
	end
	-- damage taken = raw damage / stanceMultiplier
	local stanceMultiplier = 1
	if select(2,UnitClass('player'))=='WARRIOR' then
		local stance = GetShapeshiftForm(true)
		if stance==2 then
			-- defensive stance
			if school==1 then
				stanceMultiplier = 0.9
			else
				-- Non-physical damage, apply Imp. defensive stance
				local impdef = talentcache[playerGUID].defensivestance
				stanceMultiplier = 0.9*impdef
			end
		elseif stance==3 then
			stanceMultiplier = 1.1
		end
		rawMultiplier = rawMultiplier*stanceMultiplier
	end
	return rawMultiplier, armorMultiplier, stanceMultiplier
end

--[[
7/30 20:48:11.734  SWING_DAMAGE,0xF130005C190008E7,"Halazzi",0x10a48,0x0000000000540733,"Tifi",0x511, 2754,1,0,278,0,nil,nil,nil
7/30 20:22:00.812  SWING_DAMAGE,0xF130005C180008BE,"Nalorakk",0x10a48,0x0000000000540733,"Tifi",0x511,3085,1,0,277,0,nil,nil,nil
7/30 20:32:13.171  SWING_DAMAGE,0xF130005C1A0008E5,"Jan'alai",0x10a48,0x0000000000540733,"Tifi",0x511,2907,1,0,0,929,nil,nil,1
7/30 20:32:36.578  SWING_DAMAGE,0xF130005C1A0008E5,"Jan'alai",0x10a48,0x0000000000540733,"Tifi",0x511,3522,1,0,0,0,nil,nil,1
7/30 20:34:15.796  SWING_DAMAGE,0xF130005C1A0008E5,"Jan'alai",0x10a48,0x0000000000540733,"Tifi",0x511,1633,1,0,0,0,nil,nil,1
8/7 19:53:30.531  SWING_DAMAGE,0xF13000596300067C,"Aqueousbrut",0x410a48,0x0000000000540733,"Tifi",0x511,3489,8,0,0,0,nil,nil,nil
--]]
function OneHitWonder:SWING_DAMAGE(timestamp, srcGUID,srcName,srcFlags, destGUID,destName,destFlags, 
	amount, school, resisted, blocked, absorbed, critical, glancing, crushing)
	if destGUID~=playerGUID then
		return
	end

	-- only consider NPCs
	local mobType = GetMobType(srcGUID)
	if not mobType then
		return
	end
	-- We need to compute the raw damage.
	self:Print(amount, school, resisted, blocked, absorbed, critical, glancing, crushing)
	if glancing then 
		self:Print("glancing o.O")
		return 
	end
	blocked = blocked or 0
	absorbed = absorbed or 0
	resisted = resisted or 0
	local srcLevel = guidreg:GetLevel(srcGUID) or (srcGUID==UnitGUID('target') and UnitLevel('target'))
	if not srcLevel then
		return
	end
	if srcLevel==-1 then
		-- skull mobs
		srcLevel = 73
	end
	-- amount = round((raw-resisted)*rawMultiplier) - absorbed - blocked
	local damage = amount + absorbed + blocked
	-- Get multipliers.
	local rawMultiplier, armorMultiplier, stanceMultiplier = self:GetDamageMultipliers(srcLevel, school)
	local critcrush = 1
	if crushing then
		critcrush = 1.5
	elseif critical then
		critcrush = 2
	end
	rawMultiplier = rawMultiplier*critcrush

	-- Rounding needs to be factored in.
	-- Ex: dmg = 10, m = 0.5 (armor==base). The maximum value for m*r = 10.5, so r_max = 10.5/m = 21, r_min = 9.5/0.5 = 19. 
	local rawDamageMin, rawDamageMax = (damage-0.5)/rawMultiplier, (damage+0.5)/rawMultiplier

	-- The following is not exact. There are 4 ways to resist, 25%, 50%, 75%, 100%. 
	-- The formula is: 
	-- resisted = round(raw*resistMultiplier)
	-- unresisted = raw - resisted
	-- damage = round(unresisted/dmgMultiplier) - absorbed
	-- So there are 2 round's, but we'll only consider one here.
	-- Alternatively, if resisted>0, we could try to figure out the resist percentage.
	rawDamageMin = rawDamageMin + resisted 
	rawDamageMax = rawDamageMax + resisted 

	self:StoreMeleeHit(timestamp, srcGUID, srcLevel, rawDamageMin, rawDamageMax, school, crushing)

	self:Printf('round([%.2f,%.2f]raw * %.1fcrit * %.4farmor * %.2fstance ) - %iblock - %iabsorb = %i', 
		rawDamageMin, rawDamageMax, critcrush, armorMultiplier, stanceMultiplier, blocked, absorbed, amount)
end


--[[
7/30 20:48:02.203  SWING_MISSED,0xF130005C190008E7,"Halazzi",0x10a48,0x0000000000540733,"Tifi",0x511,DODGE
7/30 20:48:04.625  SWING_MISSED,0xF130005C190008E7,"Halazzi",0x10a48,0x0000000000540733,"Tifi",0x511,MISS
7/30 20:48:05.609  SWING_MISSED,0xF130005C190008E7,"Halazzi",0x10a48,0x0000000000540733,"Tifi",0x511,PARRY
function OneHitWonder:SWING_MISSED(timestamp, srcGUID,srcName,srcFlags, destGUID,destName,destFlags, missType)
	-- "DODGE", "ABSORB", "RESIST", "PARRY", "MISS", "BLOCK", "REFLECT", "DEFLECT"
	if destGUID~=playerGUID then
		return
	end
end
--]]

--[[
7/30 20:48:06.953  SPELL_DAMAGE,0xF130005C190008E7,"Halazzi",0x10a48,0x0000000000540733,"Tifi",0x511, 43267,"Säbelhieb",0x1, 6581,1,0,278,1919,nil,nil,nil
--]]
--[[
function OneHitWonder:SPELL_DAMAGE(timestamp, srcGUID,srcName,srcFlags, destGUID,destName,destFlags, spellID,spellName,spellSchool, 
	amount, school, resisted, blocked, absorbed, critical, glancing, crushing)
	if destGUID~=playerGUID then
		return
	end
end
--]]

--[[
7/30 20:48:06.921  SPELL_MISSED,0xF130005C190008E7,"Halazzi",0x10a48,0x0000000000194965,"Malfureon",0x512,43267,"Säbelhieb",0x1,MISS
7/30 20:48:13.031  SPELL_MISSED,0xF130005C190008E7,"Halazzi",0x10a48,0x0000000000540733,"Tifi",0x511,43267,"Säbelhieb",0x1,DODGE
7/30 20:21:56.843  SPELL_MISSED,0xF130005C180008BE,"Nalorakk",0x10a48,0x0000000000540733,"Tifi",0x511,42389,"Zerfleischen",0x1,DODGE
--]]
--[[
function OneHitWonder:SPELL_MISSED(timestamp, srcGUID,srcName,srcFlags, destGUID,destName,destFlags, spellID, spellName, spellSchool, missHow)
	if destGUID~=playerGUID then
		return
	end
end
--]]





